package com.springboot.fram.network;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.net.URLEncoder;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;

@Slf4j
public class HttpUtils {

    /**
     * 请求成功相应码
     */
    public static int STATUS_CODE = 200;
    private static final String HTTP = "http";
    private static final String HTTPS = "https";

    /**
     * 读取超时时间
     */
    public static int SOCKET_TIME_OUT = 5000;
    /**
     * 链接超时时间
     */
    public static int CONNECT_TIME_OUT = 5000;


    public static String doGet(String url) {
        return httpClientGet(url);
    }

    public static String doGet(CommRequestBody requestBody) {
        Gson gson = new Gson();
        return doGet(requestBody, String.class, gson);
    }


    public static <T> T doGet(CommRequestBody requestBody, Class<T> clazz) {
        Gson gson = new Gson();
        return doGet(requestBody, clazz, gson);
    }


    /**
     * @param requestBody
     * @return
     * @throws IOException
     */
    public static <T> T doGet(CommRequestBody requestBody, Class<T> clazz, Gson gson) {
        CloseableHttpClient httpClient = wrapClient(requestBody.getHost());
        HttpRequestBase request = new HttpGet(buildUrl(requestBody.getHost(), requestBody.getPath(), requestBody.getQuerys()));
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(requestBody.getSocketTimeOut())
                .setConnectTimeout(requestBody.getConnTimeOut()).build();
        request.setConfig(requestConfig);
        // 设置请求头
        if (requestBody.getHeaders() != null) {
            for (Map.Entry<String, String> e : requestBody.getHeaders().entrySet()) {
                request.addHeader(e.getKey(), e.getValue());
            }
        }
        String result = result(httpClient, request);
        return gson.fromJson(result, clazz);
    }

    /**
     * post form
     *
     * @param requestBody
     * @return
     * @throws IOException
     */
    public static <T> T doPostForm(RequestFormBody requestBody, Class<T> clazz, Gson gson) throws IOException {
        CloseableHttpClient httpClient = wrapClient(requestBody.getRequestBody().getHost());
        HttpEntityEnclosingRequestBase request = new HttpPost(buildUrl(requestBody.getRequestBody().getHost(), requestBody.getRequestBody().getPath(), requestBody.getRequestBody().getQuerys()));
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(requestBody.getRequestBody().getSocketTimeOut())
                .setConnectTimeout(requestBody.getRequestBody().getConnTimeOut()).build();
        request.setConfig(requestConfig);
        // 设置请求头
        if (requestBody.getRequestBody().getHeaders() != null) {
            for (Map.Entry<String, String> e : requestBody.getRequestBody().getHeaders().entrySet()) {
                request.addHeader(e.getKey(), e.getValue());
            }
        }
        // 设置请求体
        if (requestBody.getBodys() != null) {
            List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();
            for (Map.Entry<String, String> e : requestBody.getBodys().entrySet()) {
                nameValuePairList.add(new BasicNameValuePair(e.getKey(), e.getValue()));
            }
            UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
            formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
            request.setEntity(formEntity);
        }
        String result = result(httpClient, request);
        T t = gson.fromJson(result, clazz);
        return t;
    }

    /**
     * 默认gson
     *
     * @param requestBody
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T doPostAppJson(RequestAppJsonBody requestBody, Class<T> clazz) {
        Gson gson = new Gson();
        return doPostAppJson(requestBody, clazz, gson);
    }

    /**
     * application/json
     *
     * @param requestBody
     * @return
     * @throws IOException
     */
    public static <T> T doPostAppJson(RequestAppJsonBody requestBody, Class<T> clazz, Gson gson) {
        CloseableHttpClient httpClient = wrapClient(requestBody.getRequestBody().getHost());
        HttpEntityEnclosingRequestBase request = new HttpPost(buildUrl(requestBody.getRequestBody().getHost(), requestBody.getRequestBody().getPath(), requestBody.getRequestBody().getQuerys()));
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(requestBody.getRequestBody().getSocketTimeOut())
                .setConnectTimeout(requestBody.getRequestBody().getConnTimeOut()).build();
        request.setConfig(requestConfig);
        request.setHeader("Content-type", "application/json");
        if (requestBody.getRequestBody().getHeaders() != null) {
            for (Map.Entry<String, String> e : requestBody.getRequestBody().getHeaders().entrySet()) {
                request.addHeader(e.getKey(), e.getValue());
            }
        }
        if (StringUtils.isNotBlank(requestBody.getDataJson())) {
            // 第三步：给httpPost设置JSON格式的参数
            StringEntity requestEntity = new StringEntity(requestBody.getDataJson(), "utf-8");
            requestEntity.setContentEncoding("UTF-8");
            requestEntity.setContentType("application/json");
            request.setEntity(requestEntity);
        }
        String result = result(httpClient, request);
        T t = gson.fromJson(result, clazz);
        return t;
    }

    /**
     * Post stream
     *
     * @param requestBody
     * @return
     * @throws Exception
     */
    public static <T> T doPostStream(RequestStreamBody requestBody, Class<T> clazz, Gson gson) {
        CloseableHttpClient httpClient = wrapClient(requestBody.getRequestBody().getHost());
        HttpEntityEnclosingRequestBase request = new HttpPost(buildUrl(requestBody.getRequestBody().getHost(), requestBody.getRequestBody().getPath(), requestBody.getRequestBody().getQuerys()));
        request.setHeader("Accept", "*/*");
        if (requestBody.getRequestBody().getHeaders() != null) {
            for (Map.Entry<String, String> e : requestBody.getRequestBody().getHeaders().entrySet()) {
                request.addHeader(e.getKey(), e.getValue());
            }
        }
        if (requestBody.getBody() != null) {
            request.setEntity(new ByteArrayEntity(requestBody.getBody()));
        }
        String result = result(httpClient, request);
        T t = gson.fromJson(result, clazz);
        return t;
    }

    /**
     * put String
     *
     * @param requestBody
     * @param clazz
     * @param gson
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> T doPut(RequestAppJsonBody requestBody, Class<T> clazz, Gson gson) {
        CloseableHttpClient httpClient = wrapClient(requestBody.getRequestBody().getHost());
        HttpEntityEnclosingRequestBase request = new HttpPost(buildUrl(requestBody.getRequestBody().getHost(), requestBody.getRequestBody().getPath(), requestBody.getRequestBody().getQuerys()));
        for (Map.Entry<String, String> e : requestBody.getRequestBody().getHeaders().entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (StringUtils.isNotBlank(requestBody.getDataJson())) {
            request.setEntity(new StringEntity(requestBody.getDataJson(), "utf-8"));
        }
        String result = result(httpClient, request);
        T t = gson.fromJson(result, clazz);
        return t;
    }

    /**
     * Put stream
     *
     * @param requestBody
     * @return
     * @throws Exception
     */
    public static <T> T doPut(RequestStreamBody requestBody, Class<T> clazz, Gson gson) {
        CloseableHttpClient httpClient = wrapClient(requestBody.getRequestBody().getHost());
        HttpEntityEnclosingRequestBase request = new HttpPut(buildUrl(requestBody.getRequestBody().getHost(), requestBody.getRequestBody().getPath(), requestBody.getRequestBody().getQuerys()));
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(requestBody.getRequestBody().getSocketTimeOut())
                .setConnectTimeout(requestBody.getRequestBody().getConnTimeOut()).build();
        request.setConfig(requestConfig);
        request.setHeader("Accept", "*/*");
        if (requestBody.getRequestBody().getHeaders() != null) {
            for (Map.Entry<String, String> e : requestBody.getRequestBody().getHeaders().entrySet()) {
                request.addHeader(e.getKey(), e.getValue());
            }
        }
        if (requestBody.getBody() != null) {
            request.setEntity(new ByteArrayEntity(requestBody.getBody()));
        }
        String result = result(httpClient, request);
        T t = gson.fromJson(result, clazz);
        return t;
    }

    /**
     * Delete
     *
     * @param requestBody
     * @param clazz
     * @param gson
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> T doDelete(CommRequestBody requestBody, Class<T> clazz, Gson gson)
            throws Exception {
        CloseableHttpClient httpClient = wrapClient(requestBody.getHost());
        HttpRequestBase request = new HttpDelete(buildUrl(requestBody.getHost(), requestBody.getPath(), requestBody.getQuerys()));
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(requestBody.getSocketTimeOut())
                .setConnectTimeout(requestBody.getConnTimeOut()).build();
        request.setConfig(requestConfig);
        for (Map.Entry<String, String> e : requestBody.getHeaders().entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }
        String result = result(httpClient, request);
        T t = gson.fromJson(result, clazz);
        return t;
    }

    /**
     * 构造URL请求连接
     *
     * @param host
     * @param path
     * @param querys
     * @return
     */
    private static String buildUrl(String host, String path, Map<String, String> querys) {
        StringBuilder sbUrl = new StringBuilder();
        sbUrl.append(host);
        if (StringUtils.isNotBlank(path)) {
            sbUrl.append(path);
        }
        if (null != querys) {
            StringBuilder sbQuery = new StringBuilder();
            for (Map.Entry<String, String> query : querys.entrySet()) {
                if (0 < sbQuery.length()) {
                    sbQuery.append("&");
                }
                if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
                    sbQuery.append(query.getValue());
                }
                if (!StringUtils.isBlank(query.getKey())) {
                    sbQuery.append(query.getKey());
                    if (!StringUtils.isBlank(query.getValue())) {
                        sbQuery.append("=");
                        try {
                            sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
                        } catch (UnsupportedEncodingException e) {
                            throw new RuntimeException("URLEncoder.encode 异常！");
                        }
                    }
                }
            }
            if (0 < sbQuery.length()) {
                sbUrl.append("?").append(sbQuery);
            }
        }

        return sbUrl.toString();
    }


    /**
     * ssl授信
     *
     * @param host
     * @return
     */
    private static CloseableHttpClient wrapClient(String host) {
        // 区分http/https
        if (host.startsWith(HTTPS)) {
            return ssl();
        }
        CloseableHttpClient httpClient = HttpClients.createDefault();
        return httpClient;
    }

    /**
     * https 授信
     *
     * @return
     */
    private static CloseableHttpClient ssl() {
        SSLContextBuilder builder = new SSLContextBuilder();
        // 全部信任 不做身份鉴定
        try {
            builder.loadTrustMaterial(null, new TrustStrategy() {
                @Override
                public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                    return true;
                }
            });
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build(), new String[]{"SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.2"}, null, NoopHostnameVerifier.INSTANCE);
            Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register(HTTP, PlainConnectionSocketFactory.getSocketFactory())
                    .register(HTTPS, SSLConnectionSocketFactory.getSocketFactory())
                    .build();
            PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
            // max connection 整个连接池的最大连接数,默认20，cm.setMaxTotal(300);
            CloseableHttpClient httpClient = HttpClients.custom()
                    .setSSLSocketFactory(sslsf)
                    .setConnectionManager(cm)
                    .setConnectionManagerShared(true)
                    .build();
            return httpClient;
        } catch (Exception e) {
            throw new RuntimeException("ssl 授信异常");
        }
    }


    /**
     * 请求结果
     *
     * @param httpClient
     * @param request
     * @return
     */
    public static String result(CloseableHttpClient httpClient, HttpRequestBase request) {
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(request);
            String result = null;
            try {
                result = EntityUtils.toString(response.getEntity(), "UTF-8");
                if (STATUS_CODE == response.getStatusLine().getStatusCode()) {
                    return result;
                } else {
                    throw new RuntimeException("接口响应码:" + response.getStatusLine().getStatusCode() + ",内容:" + result);
                }
            } catch (IOException e) {
                throw new RuntimeException("请求结果值转换异常:" + e.getMessage());
            }
        } catch (ConnectTimeoutException e) {
            throw new RuntimeException("接口超时");
        } catch (SocketTimeoutException e) {
            throw new RuntimeException("读取接口数据超时");
        } catch (IOException e) {
            throw new RuntimeException("接口请求失败，请尝试检查网络环境或请求接口是否能正常访问");
        } finally {
            // 关闭响应
            try {
                if (response != null) {
                    response.close();
                }
                // 关闭连接
                httpClient.close();
            } catch (IOException e) {
                throw new RuntimeException("关闭流异常:IOException");
            }
        }
    }

    /**
     * 发起get请求，默认值
     *
     * @param url
     * @return
     */
    public static String httpClientGet(String url) {
        return httpClientGet(url, SOCKET_TIME_OUT, CONNECT_TIME_OUT);
    }

    /**
     * 发送get请求
     * 自定义超时及读取时间
     *
     * @param url
     * @param socketyimeout
     * @param connecttimeout
     * @return
     */
    public static String httpClientGet(String url, int socketyimeout, int connecttimeout) {
        // 第一步：创建HttpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketyimeout)
                .setConnectTimeout(connecttimeout).build();
        // 第二步：创建httpPost对象
        HttpRequestBase request = new HttpGet(url);
        request.setConfig(requestConfig);
        return result(httpClient, request);
    }

    /**
     * 表单提交参数
     *
     * @param url
     * @param params
     * @param <K>
     * @param <V>
     * @return
     * @throws IOException
     */
    public static <K, V extends Comparable<? super V>> String httpClientPostForm(String url, Map<K, V> params) throws IOException {
        // 第一步：创建HttpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(SOCKET_TIME_OUT)
                .setConnectTimeout(CONNECT_TIME_OUT).build();
        // 第二步：创建httpPost对象
        HttpEntityEnclosingRequestBase request = new HttpPost(url);
        request.setConfig(requestConfig);
        // 第三步：设置参数
        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
        for (Iterator iter = params.keySet().iterator(); iter.hasNext(); ) {
            String name = (String) iter.next();
            String value = String.valueOf(params.get(name));
            nvps.add(new BasicNameValuePair(name, value));
        }
        request.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
        request.setHeader("Content-type", "application/x-www-form-urlencoded");
        return result(httpClient, request);
    }


    /**
     * 发送json字符串数据
     * post
     *
     * @param url
     * @param dataJson
     * @param <T>
     * @return
     */
    public static <T> String doPostAppJson(String url, String dataJson) {
        return httpClientPostAppJson(url, dataJson, SOCKET_TIME_OUT, CONNECT_TIME_OUT);
    }

    public static <T> String doPostAppJson(String url, String dataJson, int socketyimeout, int connecttimeout) {
        return httpClientPostAppJson(url, dataJson, socketyimeout, connecttimeout);
    }

    /**
     * 发送json字符串数据
     *
     * @param url
     * @param dataJson
     * @return
     */
    public static String httpClientPostAppJson(String url, String dataJson) {
        return httpClientPostAppJson(url, dataJson, SOCKET_TIME_OUT, CONNECT_TIME_OUT);
    }

    /**
     * 发送json字符串数据
     * 自定义超时读取时间
     *
     * @param url
     * @param dataJson
     * @param socketyimeout
     * @param connecttimeout
     * @return
     */
    public static String httpClientPostAppJson(String url, String dataJson, int socketyimeout, int connecttimeout) {
        // 第一步：创建HttpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketyimeout)
                .setConnectTimeout(connecttimeout).build();
        // 第二步：创建httpPost对象
        HttpEntityEnclosingRequestBase request = new HttpPost(url);
        request.setConfig(requestConfig);
        // 设置参数
        // 第三步：给httpPost设置JSON格式的参数
        StringEntity requestEntity = new StringEntity(dataJson, "UTF-8");
        requestEntity.setContentEncoding("UTF-8");
        request.setHeader("Content-type", "application/json");
        request.setEntity(requestEntity);
        return result(httpClient, request);
    }

}
